package DTask::Task;
use 5.018;
use warnings;
use Tree::Simple;
use Tree::Simple::View::ASCII;
use Data::Dumper;
use Mango::BSON ':bson';

use Mojo::JSON qw(true false);
use DTask::Utils qw(wrapMangoOp);
use Carp qw(carp);
use DTask::Cache;
use Time::HiRes qw(gettimeofday);

my ($db, $task_col);

sub _pseudo_tower_todo_guid(;$)
{
	if (defined $_[0])
	{
		return "taskid". $_[0];
	}
	else
	{
		my ($a, $b) = gettimeofday();
		return 'pesudo'. $a. $b;
	}
}

sub _bson_oid($)
{
	my $id = shift;
	my $oid = eval { bson_oid $id };
	warn "_bson_oid: $@" if $@;
	return $oid;
}

sub setMango
{
	my $mango = shift;

	#TODO maybe add debug mode db dtask_debug
	$db = $mango->db('dtask');
	$task_col = $db->collection('task');

	DTask::Cache::setMango($mango);
}

#block
sub init
{
	warn "task collection init done";
}

sub getTowerTodosByLink
{
	my ($target, $target_id, $cb) = @_;
	getTasksByLink($target, $target_id, sub {
		my (@task_ids) = @_;
		
		my $delay = Mojo::IOLoop->delay(sub {
				my ($delay, @tower_todo_guid) = @_;
				@tower_todo_guid = grep { $_ if defined $_  } @tower_todo_guid;
				warn Dumper \@tower_todo_guid;
				warn 'getTowerTodosByLink: done';

				$cb->(@tower_todo_guid);
		});

		for my $task_id (@task_ids)
		{
			my $end = $delay->begin;
			getTowerTodo($task_id, sub {
				my ($todo_guid) = @_;
				$delay->pass($todo_guid);
				$end->();	
			});	
		}
	});
}


#db 1
sub getTasksByLink
{
	my ($target, $target_id, $cb) = @_;
	$target_id .= '';
	my $collection = $db->collection($target);

	#db 1
	#db end
	$collection->find_one({_id => $target_id }, {_id => 0, task => 1}, sub {
		my $find_one = wrapMangoOp(@_);
		my @task_ids;
		if ($find_one)
		{
			my $task_oids = $find_one->{task};
			@task_ids = map { $_->to_string } @$task_oids;
		}
		else
		{
			warn "$target not has $target_id";
		}

		if (@task_ids)
		{
			warn "getTasksByLink $target $target_id tasks: ". join(', ',  @task_ids);
		}
		else
		{
			warn "getTasksByLink $target $target_id no task";
		}
		$cb->(@task_ids);
	});
}

#example: DTask::Task::new;
#db 1
sub new
{
	my ($cb) = @_;
	#db 1

	$task_col->insert({createAt => bson_time, tower_todo => _pseudo_tower_todo_guid() }, sub {
		my $oid = wrapMangoOp(@_);
		warn "new: new task $oid";
		$cb->($oid->to_string);
	});
}

sub newFixBug
{
	my ($bugs, $cb) = @_;

	my @bugs= map { $_ .''  } @$bugs;

	if (@bugs)
	{
		my $bugs_str = join ", ", @bugs;
		$task_col->insert(
			{
				createAt => bson_time ,
				name => 'FIX-BUG: '. $bugs_str,
				link => {
					bugzilla => \@bugs
				}
			},
		sub {
			my $oid = wrapMangoOp(@_);
			warn "newFixBug $bugs_str task $oid";

			my $task_id = $oid->to_string;
			my $delay = Mojo::IOLoop->delay(sub {
				warn "link all bug done";
				$cb->($oid->to_string);
			});

			for my $bug_id (@bugs)
			{
				my $end = $delay->begin;
				linkWith($task_id, 'bugzilla',$bug_id, sub {
					warn "link task with bug $bug_id";
					$end->();	
				});

			}
		});

	}
	
}

sub _valid_tower_guid
{
	my $guid = shift;
	$guid =~ /[0-9a-f]{32}/;
}

#db 1 or 2

sub newWithTowerTodo
{
	my ($todo_guid, $cb) = @_;
	if (_valid_tower_guid($todo_guid))
	{
		getByTowerTodo($todo_guid, sub {
			my ($task_id) = @_;
			if ($task_id)
			{
				warn "newWithTowerTodo: task already exists, id $task_id";
				$cb->($task_id);
			}
			else
			{
				$task_col->insert({createAt => bson_time ,  tower_todo => $todo_guid }, sub {
					my $task_oid = wrapMangoOp(@_);	
					$cb->($task_oid->to_string);
					warn "newWithTowerTodo: insert new task $task_oid done";
				});

			}
		});
	}
	else
	{
		carp "invalid tower todo guid $todo_guid";
		$cb->();
	}
}

sub getByTowerTodo
{
	my ($todo_guid, $cb) = @_;
	if (_valid_tower_guid($todo_guid))
	{
		$task_col->find_one({tower_todo => $todo_guid}, {_id => 1 }, sub {
			my $doc = wrapMangoOp(@_);
			if (my $task_oid = $doc->{_id})
			{
				my $task_id = $task_oid->to_string;
				warn "getByTowerTodo: task id $task_id";
				$cb->($task_id);
			}
			else
			{
				warn "getByTowerTodo: no task link with tower todo $todo_guid";
				$cb->();
			}
		});
	}
	else
	{
		my $err_msg = "invalid todo guid $todo_guid";
		carp "getByTowerTodo: $err_msg";
		$cb->(undef, $err_msg);
	}
}

sub getInfoByTowerTodo
{
	my ($todo_guid, $cb) = @_;
	if (_valid_tower_guid($todo_guid))
	{
		$task_col->find_one({tower_todo => $todo_guid}, sub {
			my $doc = wrapMangoOp(@_);
			warn 'getInfoByTowerTodo dump $doc';
			warn Dumper $doc;
			if ($doc)
			{
				$cb->($doc);
			}
			else
			{
				warn "getInfoByTowerTodo: no task link with tower todo $todo_guid";
				$cb->();
			}
		});
	}
	else
	{
		my $err_msg = "invalid todo guid $todo_guid";
		carp "getByTowerTodo: $err_msg";
		$cb->(undef, $err_msg);
	}
}


sub getTowerTodo
{
	my ($task_id, $cb) = @_;
	$task_col->find_one({_id => _bson_oid $task_id}, {tower_todo => 1 }, sub {
		my $doc = wrapMangoOp(@_);
		if (my $todo_guid = $doc->{tower_todo})
		{
			if (_valid_tower_guid($todo_guid))
			{
				$cb->($todo_guid);
			}
			else
			{
				$cb->();
			}
		}
		else
		{
			$cb->();
		}
	});
}

sub setTowerTodo
{
	my ($task_id, $todo_guid, $cb) = @_;
	my $task_oid = _bson_oid $task_id;
	#find tower todo guid remove it
	$task_col->find_one({tower_todo => $todo_guid}, {_id => 1}, sub {
		my $doc = wrapMangoOp(@_);
		if (my $_task_oid = $doc->{_id})
		{
			#tower todo guid already exists
			if ($_task_oid->to_string eq $task_id)
			{
				#no need change
				warn "setTowerTodo: no need change";
				$cb->(false);
			}
			else
			{
			
				warn "setTowerTodo: task $_task_oid with the same tower todo";
				$task_col->update({_id => $_task_oid}, { '$unset' => { tower_todo => '' }}, sub {
					wrapMangoOp(@_);
					$task_col->update({_id => $task_oid}, { '$set' => {tower_todo => $todo_guid}}, sub {
						warn "setTowerTo: done"	;
						wrapMangoOp(@_);
						$cb->(true);
					});	
				});
			}
		}
		else
		{
			warn "setTowerTodo: no task with tower todo $todo_guid";
			$task_col->update({_id => $task_oid}, { '$set' => {tower_todo => $todo_guid}}, sub {
				wrapMangoOp(@_);
				warn "setTowerTo: done"	;
				$cb->(true);
			});	

		}
	});
}

sub removeTowerTodo
{
	my ($task_id, $cb) = @_;
	$task_col->update({_id => _bson_oid $task_id }, {'$set' => { tower_todo => _pseudo_tower_todo_guid($task_id) }}, sub {
		my $doc = wrapMangoOp(@_);
		warn "removeTowerTodo task $task_id done";
		$cb->( $doc->{n} > 0 ? true : false );
	});
}


sub remove
{
	my ($task_id, $cb) = @_;
	#TODO
	#解除所有 children 的 parent 关系，解除parent 的 child
	#
	
	my $delay = Mojo::IOLoop->delay(
		sub {
			my $delay = shift;
			my $end = $delay->begin;
			getLinks($task_id, sub {
				my ($links)	= @_;
				$delay->pass($links);
				$end->();
			});
		},

		sub {
			#解除所有 link 关系
			my ($delay, $links) = @_;
			if ( keys %$links )
			{
				for my $target ( keys %$links )
				{
					for my $target_id ( @{ $links->{$target} } )
					{
						unlinkWith($task_id, $target, $target_id, $delay->begin );
					}
				}

			}
			else
			{
				#skip
				$delay->begin->();
			}
		},
		sub {
			my ($delay) = @_;
			warn 'unlink all done';
			$task_col->remove({_id => _bson_oid $task_id } => sub {
				my $doc = wrapMangoOp(@_);
				if ($doc->{ok} && $doc->{n} > 0 )
				{
					warn "task $task_id removed";
					$cb->(true);
				}
				else
				{
					$cb->(false);
				}
			});
		}
	);

}


sub allLink
{
	my ($task_ids, $links, $cb) = @_;
	unless (@$task_ids)
	{
		$cb->();
		return;
	}
	my @task_oids = map { _bson_oid $_ } @$task_ids;
	my $delay = Mojo::IOLoop->delay(
		sub {
			#task update link
			my ($delay)  = @_;
			my $links_added = {};
			#fill $links_added
			for my $target ( keys %$links )
			{
				my @target_ids = map { $_ .'' } @{ $links->{ $target } };
				if (@target_ids)
				{
					$links_added->{ 'link.'. $target } = { '$each' => \@target_ids };
				}
			}

			#$links_added = {};

			for my $task_oid ( @task_oids )
			{

				if (keys %$links_added )
				{
					my $end = $delay->begin;
					$task_col->update({_id => $task_oid }, { '$addToSet' => $links_added } => sub {
						warn 'task add links, dump $links_added';
						warn Dumper $links_added;
						wrapMangoOp(@_);
						$end->();
					});
				}
			}
		},
		sub {
			warn 'task update link done';
			#target collection update or insert document
			my ($delay)	= @_;
			for my $target (keys %$links )
			{
				my $collection = $db->collection($target);
				for my $target_id (@{ $links->{$target} } )
				{
					_target_set_tasks($target, $target_id,\@task_oids , $delay->begin);
				}
				
			}
		}

	);

	$delay->on('finish', sub {
		warn 'target collection update or insert done';
		$cb->();
	});

}


sub _target_set_tasks
{
	my ($target, $target_id,$task_oids, $cb) = @_;
	$target_id .= '';
	my $collection = $db->collection($target);

	$collection->update( { _id => $target_id }, {'$addToSet' => {task => {'$each' => $task_oids }}}  => sub {
		my $doc = wrapMangoOp(@_);
		if ( $doc->{ok} && $doc->{n} > 0 )
		{
			warn "$target has $target_id, update";
			$cb->();
		}
		else
		{
			warn "$target has not $target_id, insert";
			$collection->insert({ _id => $target_id, task => $task_oids , createAt => bson_time } => sub {
				wrapMangoOp(@_);
				$cb->();
			});
		}
	});
}

#db 3
sub linkWith
{
	my ($task_id, $target, $target_id, $cb) = @_;
	my $task_oid = _bson_oid $task_id;

	$task_col->update( {_id => $task_oid }, { '$addToSet' => { 'link.'. $target ,  $target_id} } => sub {
		my $doc = wrapMangoOp(@_);
		if ( $doc->{ok} && $doc->{n} > 0 )
		{
			if ($doc->{nModified} > 0 )
			{
				_target_set_tasks($target, $target_id, [ $task_oid ], sub {
					$cb->(true);	
				});

			}
			else
			{
				$cb->(false);
			}
		}
		else
		{
			warn "linkWith task $task_id not exists";
			$cb->(undef, "linkWith failed , task $task_id not exists");
		}
	});
}

sub unlinkWith
{
	my ($task_id, $target, $target_id, $cb) = @_;
	my $task_oid = _bson_oid $task_id;
	$target_id .= '';
	my $collection = $db->collection($target);
	warn "unlinkWith: unlink $target $target_id with task $task_id";

	$collection->update({_id => $target_id }, { '$pull' => { task => $task_oid } } => sub {
		my $pull_task = wrapMangoOp(@_);
		warn Dumper $pull_task;

		$task_col->update({ _id => $task_oid }, { '$pull' => { 'link.' . $target, $target_id } } => sub {
			my $pull_link = wrapMangoOp(@_);

			warn Dumper $pull_link;

			if ($pull_link->{ok} && $pull_task->{ok})
			{
				if ($pull_link->{nModified} > 0 || $pull_task->{nModified} > 0 )
				{
					$cb->(true);
				}
				else
				{
					$cb->(false);
				}
			}
			else
			{
				$cb->(undef, "unlink failed");
			}
		});
	});
}


sub unlinkWithAllTasks
{
	my ($target, $target_id, $cb) = @_;
	$target_id .= '';
	warn "unlinkWithAllTasks: unlink $target $target_id with all linked task";
	getTasksByLink($target, $target_id, sub {
		my (@task_ids) = @_;
		if (@task_ids)
		{
			my $delay = Mojo::IOLoop->delay(sub {
				$cb->(true);	
			} );
			for my $task_id (@task_ids)
			{
				my $end = $delay->begin;
				unlinkWith($task_id, $target, $target_id, sub {
					$end->();
				});
			}

		}
		else
		{
			$cb->(false);
		}
	});

}

sub getInfo
{
	my ($task_id, $cb) = @_;
	my $task_oid = _bson_oid $task_id;
	$task_col->find_one({ _id => $task_oid } => sub {
		my $doc = wrapMangoOp(@_);
		unless ($doc)
		{
			warn "task $task_id do not have any info";
		}
		$cb->($doc);
	});
}

sub getLinksInfoByTowerTodo
{
	my ($todo_guid, $cb) = @_;
	getByTowerTodo($todo_guid, sub {
		my ($task_id) = @_;		
		if ($task_id)
		{
			getLinksInfo($task_id, sub {
				my (@info)	= @_;
				$cb->(@info);
			});
		}
		else
		{
			$cb->();
		}
	});
}


sub getLinksInfo
{
	my ($task_id, $cb) = @_;
	warn "getLinksInfo task id $task_id";
	getLinks($task_id, sub {
		my ($links) = @_;
		warn "getLinksInfo";
		my $delay = Mojo::IOLoop->delay(sub {
			my ($delay, @infos) = @_;
			say $_ for @infos;
			warn "getLinksInfo done";
			$cb->(@infos);
		});


		for my $target ( keys %$links)
		{
			for my $target_id ( @{ $links->{$target} } )
			{
				my $end = $delay->begin;
				DTask::Cache::getLinkInfo($target, $target_id, $delay, $end);
			}

		}

	});
}


#db 1
sub getLinks
{
	my ($task_id, $cb) = @_;
	warn 'getLinks task '. $task_id;
	my $task_oid = _bson_oid $task_id;
	$task_col->find_one({_id => $task_oid}, {_id => 0, link => 1} => sub {
		my $doc = wrapMangoOp(@_);

		my $links;
		if ($links = $doc->{link})
		{
			warn 'getLinks dump $links';
			warn Dumper $links;
		}
		else
		{
			warn "task $task_id do not have any links";
		}
		$cb->($links);
	});

}

#db 1
# TODO 废弃掉 用 getTasksByLink
sub fromLink
{
	my ($class, $target, $target_id) = @_;
	warn "fromLink target $target target_id $target_id";
	my $collection = $db->collection($target);

	#db 1
	my $find_one = $collection->find_one({_id => $target_id}, { _id => 0, task => 1});
	warn 'fromLink dump $find_one';
	warn Dumper $find_one;
	if ( defined (my $tasks = $find_one->{task}) )
	{
		warn "link has task";
		return map {
			bless { oid => $_ }, $class
		} @{$tasks};
		
	}
}


sub addChild
{
	my ($task_id, $sub_task_id, $cb)  = @_;
	my $sub_task_oid = _bson_oid $sub_task_id;
	my $task_oid = _bson_oid $task_id;

	$task_col->find_one({_id => $sub_task_oid}, {parent => 1, _id => 0} => sub{
		my ($collection, $err, $doc) = @_;
		if ($doc->{parent})
		{
			warn "sub task $sub_task_id already has parent, addChild failed";
			$cb->({ error => true, error_message => "sub task $sub_task_id already has parent" });
		}
		else
		{
			$task_col->update({_id => $sub_task_oid},{
				'$set' => {parent => $task_oid} } => sub {
					my ($collection, $err, $add_parent) = @_;
					$task_col->update({_id => $task_oid },
						{
							'$addToSet' => { children => $sub_task_oid }
						} => sub {
							my ($collection, $err, $add_child) = @_;
							if ($add_parent->{ok} && $add_child->{ok})
							{
								$cb->({error => false });
							}
							else
							{
								$cb->({error => true, error_message => 'addChild failed'});
							}
						}
					); #add child
			}); #add parent
		}

	});
}
sub removeChild
{
	my ($task_id, $sub_task_id, $cb)  = @_;
	my $sub_task_oid = _bson_oid $sub_task_id;
	my $task_oid = _bson_oid $task_id;
	$task_col->update({_id => $task_oid}, { '$pull' => {children => $sub_task_oid } } => sub {
		my ($collection, $err, $remove_child) = @_;
		$task_col->update({_id => $sub_task_oid}, {'$unset' => {parent => ''}} => sub {
			my ($collection, $err, $remove_parent) = @_;
			if ($remove_child->{ok} && $remove_child->{ok})
			{
				$cb->({error => false});

			}
			else
			{
				$cb->({error => true, error_message => 'removeChild failed'});

			}
		});	
	});
	
}

sub treeView
{
	my ($self) = @_;
	#get parent
	#get links
	#get childs

	my $root = $self;
	#get root task
	while(my $p = $root->getParent )
	{
		say "parent find : ". $p->{oid};
		$root = $p;
	}
	
	my $tree = $root->_get_tree;
	my $tree_view = Tree::Simple::View::ASCII->new($tree);
	$tree_view->includeTrunk(1);
	print $tree_view->expandAll;
}

sub _get_tree
{
	my $self = shift;
	my $tree = Tree::Simple->new("task($self->{oid})", Tree::Simple->ROOT);
	my @children;
	my $links = $self->getLinks;
	warn Dumper $links;
	for ( keys %$links )
	{
		my $item_str = join ", ",  @{ $links->{$_} };
		$tree->addChild(Tree::Simple->new("link($_ : $item_str)" ));
	}
	for ( $self->getChildren )
	{
		$tree->addChild( _get_tree($_) );
	}
	return $tree;

}



#db 1
sub getChildren
{
	my $self = shift;
	#db 1
	my $find_one = $task_col->find_one({_id => $self->{oid}}, {_id => 0, children => 1});
	warn "getChildren dump \$find_one";
	warn Dumper $find_one;
	my @children;
	if ( $find_one )
	{
		@children = map { bless {oid => $_ }, __PACKAGE__ } @{$find_one->{children}} ;
	}
	return @children;
}

#db 1
sub getParent
{
	my $self = shift;
	#db 1
	my $find_one = $task_col->find_one({_id => $self->{oid}}, {_id => 0, parent => 1});
	warn "getParent dump \$find_one";
	warn Dumper $find_one;
	if ($find_one->{parent} )
	{
		return bless {oid => $find_one->{parent} }, __PACKAGE__;
	}
	warn "getParent no parent";
	return;
}


#example: DTask::Task::link_finish('gerrit', '1233', 'xyzz');
sub linkFinish
{
	my ($target, $target_id,$member) = @_;
	$target_id .= '';
	my $collection = $db->collection($target);

	my $finish = $collection->update({_id => $target_id},
		{
			'$set' => { finishAt => bson_time , member => $member}
	});
	warn 'link_finish dump $finish';
	warn Dumper $finish;

	my @tasks = __PACKAGE__->fromLink($target, $target_id);
	_check_finish($_->{oid}) for @tasks;
}

#db 2 * scalar keys %$link
sub _check_finish
{
	my ($task_oid) = @_;

	#db 1
	my $find_one = $task_col->find_one({_id => $task_oid}, {_id => 0, parent => 1,children => 1,link => 1});
	warn "_check_finish dump \$find_one";
	warn Dumper $find_one;
	if ($find_one)
	{
		my $links = $find_one->{link};
		if ($links)
		{

			#db 1 * scalar keys %$link
			for my $target (keys %$links)
			{
				my $collection = $db->collection($target);

				my $ids = [];
				for my $target_id ( @{ $links->{$target} } )
				{
					push @$ids, { _id => $target_id };
				}
				warn '_check_finish dump $ids';
				warn Dumper $ids;

				#db 1
				my $find_not_finish = $collection->find_one(
					{
						'$or' => $ids,
						finishAt => { '$exists' => 0 }
					},
					{_id => 1}
				);
				warn 'linkFinish dump $find_not_finish';
				warn Dumper $find_not_finish;

				if( $find_not_finish)
				{
					warn "linkFinish find link not finish";
					return;
				}
				else
				{
					warn "linkFinish all link finish";
				}

			}
			
		}

		#db 1 + 1 * scalar keys %$links
		if ( defined (my $children = $find_one->{children}))
		{
			my $ids;
			push @$ids, {_id => @$children };
			my $find_not_finish = $task_col->find_one(
				{
					'$or' => $ids,
					finishAt => { '$exists' => 0 }
				},
				{ _id => 1}
			);
			warn 'linkFinish dump $find_not_finish';
			warn Dumper $find_not_finish;
			if ($find_not_finish)
			{
				warn "linkFinish: find child not finish";
				return;
			}
			else
			{
				warn "linkFinish: all children finish";
			}
		}

		#all link finish and all children finish
		#db 2 + 1 * ...
		$task_col->update({_id => $task_oid }, {
				'$set' => { finishAt => bson_time }
		});
		warn 'LinkFinish: task finish';

		if (defined (my $task_parent_oid = $find_one->{parent}))
		{
			warn "LinkFinish _check_finish parent";
			_check_finish($task_parent_oid);
		}
	}

}

#set not finish recursively
#db 1+
sub _set_not_finish
{
	my ($task_oid) = @_;

	#db 1
	my $unset = $task_col->update( {_id => $task_oid },
		{
			'$unset' => { finishAt => '' }
	});
	warn '_set_not_finish dump $unset';
	warn Dumper $unset;

	if ( $unset->{nModified} > 0 )
	{
		my $find_one = $task_col->find_one({_id => $task_oid}, {parent => 1, _id => 0});
		if (defined (my $task_parent_oid = $find_one->{parent}))
		{
			warn "_set_not_finish parent";
			_set_not_finish($task_parent_oid);
		}
	}
	else
	{
		warn "not need set not finish recursively";
	}
}

sub linkNotFinish
{
	my ($target, $target_id) = @_;
	$target_id .= '';
	my $collection = $db->collection($target);
	my $not_finish = $collection->update({_id => $target_id }, {
			'$unset' => { finishAt => '' }
	});
	warn 'link_not_finish dump $not_finish';
	warn Dumper $not_finish;

	my @tasks = __PACKAGE__->fromLink($target, $target_id);
	_set_not_finish($_->{oid}) for @tasks;
}

1;

